/*************************************************
* Title：TinyToolKit
* Author：Opportunity 
* Describe：实现第一人称角色行为控制
* CreateTime：2020-05-11 09:56:17
* Version：1.0
* Modify Recorder：
*************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using TinyToolKit.Sound;
using UnityEngine;
using UnityEngine.EventSystems;
using Random = UnityEngine.Random;

namespace TinyToolKit
{
    [RequireComponent(typeof(CharacterController))]
    public class FirstPersonController : MonoBehaviour
    {
        [Header("Base")]
        [SerializeField] private float _walkSpeed = 5;
        [SerializeField] private bool _allowRun = true;
        [SerializeField] private float _runSpeed = 10;
        [SerializeField] private float _runStepLength = 0.7f;
        [SerializeField] private bool _allowJump = true;
        [SerializeField] private float _jumpSpeed = 10;
        [SerializeField] private float _stickToGroundForce = 10;
        [SerializeField] private float _gravityMultiplier = 2;
        
        [Header("Mouse")]
        [SerializeField] private bool _isLockMouse;
        [SerializeField] private bool _ignoreUI = true; 
        [SerializeField] private MouseCtr mouseCtr;
        
        [Header("FOV")]
        [SerializeField] private bool _useFovKick = true;
        [SerializeField] private FOVKick _fovKick;
        
        [Header("Bob")]
        [SerializeField] private bool _useHeadBob = true;
        [SerializeField] private CurveControlledBob _headBob;
        [SerializeField] private LerpContolledBob _jumpBob;
        [SerializeField] private float _stepInterval = 5;

        [Header("Sound")]
        [SerializeField,Range(0,1)] private float _soundVolume = 0.5f;
        [SerializeField] private AudioClip _jumpSound;
        [SerializeField] private AudioClip _landSound;
        [SerializeField] private AudioClip[] _footStepSounds;

        private Camera _camera;
        private bool _isWalking;
        private float _yRotation;
        private Vector2 _input;
        private Vector3 _moveDir;
        private CharacterController _characterController;

        //在最后的CharacterController.Move调用期间，胶囊体的哪个部分与周围环境相碰撞
        private CollisionFlags _collisionFlags;
        private bool _previouslyGrounded = true;
        private Vector3 _oriCamPos;
        private float _stepCycle;
        private float _nextStep;
        private bool _jumping;
        private TSoundManager _soundManager;

        private void Start()
        {
            _characterController = gameObject.GetComponent<CharacterController>();
            _soundManager = TinyTool.Instance.GetManager<TSoundManager>();

            _camera = Camera.main;
            if (_camera != null)
            {
                _oriCamPos = _camera.transform.localPosition;
                _fovKick.Setup(_camera);
                _headBob.Setup(_camera, _stepInterval);
                mouseCtr.Init(transform, _camera.transform);
            }

            mouseCtr.SetCursorLock(_isLockMouse);
        }

        private void Update()
        {
            RotateView();

            //角色跳起后，落地的瞬间
            if (!_previouslyGrounded && _characterController.isGrounded)
            {
                //模拟落地时，视角轻微抖动的效果
                StartCoroutine(_jumpBob.DoBobCycle());
                if (_landSound != null) 
                {
                    _soundManager.PlayFxSound(_landSound, _soundVolume);
                    _nextStep = _stepCycle + 0.5f;
                }
                _moveDir.y = 0f;
                _jumping = false;
            }

            //当角色从高空坠落的瞬间
            if (!_characterController.isGrounded && !_jumping && _previouslyGrounded)
            {
                _moveDir.y = 0f;
            }

            //记录落地状态，方便下一帧比较
            _previouslyGrounded = _characterController.isGrounded;
        }

        private void FixedUpdate()
        {
            float speed = GetMoveSpeed();
            //角色当前移动的目标方向
            Vector3 desiredMove = transform.forward * _input.y + transform.right * _input.x;

            //当角色在斜面上行走时，速度会减慢
            RaycastHit hitInfo;
            Physics.SphereCast(transform.position, _characterController.radius, Vector3.down, out hitInfo,
                _characterController.height / 2f, Physics.AllLayers, QueryTriggerInteraction.Ignore);
            //计算前进的目标方向在地面平面上的投影
            desiredMove = Vector3.ProjectOnPlane(desiredMove, hitInfo.normal).normalized;

            _moveDir.x = desiredMove.x * speed;
            _moveDir.z = desiredMove.z * speed;

            //只有当前触地的情况下，可以跳跃
            if (_characterController.isGrounded)
            {
                _moveDir.y = -_stickToGroundForce;
                if (_allowJump && Input.GetButtonDown("Jump"))
                {
                    _jumping = true;
                    _moveDir.y = _jumpSpeed;
                    if (_jumpSound != null)
                        _soundManager.PlayFxSound(_jumpSound, _soundVolume);
                }
            }
            else
            {
                //Physics.gravity (0.0, -9.8, 0.0)
                _moveDir += Physics.gravity * (_gravityMultiplier * Time.fixedDeltaTime);
            }

            _collisionFlags = _characterController.Move(_moveDir * Time.fixedDeltaTime);

            HandleStepCycle(speed);
            UpdateCameraPosition(speed);
        }

        //处理播放脚步音效的周期
        private void HandleStepCycle(float speed)
        {
            if (_characterController.velocity.sqrMagnitude > 0 &&
                (Math.Abs(_input.x) > 0.01f || Math.Abs(_input.y) > 0.01f))
            {
                var stepLength = _isWalking ? 1f : _runStepLength;
                _stepCycle += (_characterController.velocity.magnitude + speed * stepLength) * Time.fixedDeltaTime;
            }

            if (_stepCycle <= _nextStep) return;

            _nextStep = _stepCycle + _stepInterval;
            PlayFootStepAudio();
        }

        //播放脚步音效
        private void PlayFootStepAudio()
        {
            if (!_characterController.isGrounded || _footStepSounds.Length == 0) return;

            //随机播放脚步声
            var index = Random.Range(1, _footStepSounds.Length);
            if (_footStepSounds[index] == null) return;
            _soundManager.PlayFxSound(_footStepSounds[index], _soundVolume);

            //和下标为0的音效替换，避免下次播放同样的脚步音效
            AudioClip tempStepSound = _footStepSounds[index];
            _footStepSounds[index] = _footStepSounds[0];
            _footStepSounds[0] = tempStepSound;
        }

        //在启用头部抖动效果时，模拟角色行走时的视角抖动
        private void UpdateCameraPosition(float speed)
        {
            if (!_useHeadBob) return;
            Vector3 newCamPos;
            if (_characterController.velocity.magnitude > 0 && _characterController.isGrounded)
            {
                float stepLength = _isWalking ? 1f : _runStepLength;
                _camera.transform.localPosition = _headBob.DoHeadBob(_characterController.velocity.magnitude + speed * stepLength);
                newCamPos = _camera.transform.localPosition;
                newCamPos.y = _camera.transform.localPosition.y - _jumpBob.Offset();
            }
            else
            {
                newCamPos = _camera.transform.localPosition;
                newCamPos.y = _oriCamPos.y - _jumpBob.Offset();
            }

            _camera.transform.localPosition =
                Vector3.Lerp(_camera.transform.localPosition, newCamPos, Time.deltaTime * 5);
        }

        //控制角色视角的旋转
        private void RotateView()
        {
            bool canRotate = !(_ignoreUI && EventSystem.current != null && EventSystem.current.IsPointerOverGameObject());

            if (canRotate)
                mouseCtr.LookRotation(transform, _camera.transform);
        }

        //获取当前角色移动的速度
        private float GetMoveSpeed()
        {
            float horizontal = Input.GetAxis("Horizontal");
            float vertical = Input.GetAxis("Vertical");
            _input = new Vector2(horizontal, vertical);
            if (_input.sqrMagnitude > 1)
                _input.Normalize();
            
            float speed = _walkSpeed;
            bool wasWalking = _isWalking;
            if (_allowRun && Input.GetKey(KeyCode.LeftShift) && _characterController.velocity.sqrMagnitude > 0)
            {
                _isWalking = false;
                speed = _runSpeed;
            }
            else
            {
                _isWalking = true;
                speed = _walkSpeed;
            }
            
            //启用视野变化的情况下，当运动速度发生变化的时候，产生一个视角范围变化
            if (_useFovKick && _isWalking != wasWalking && _characterController.velocity.sqrMagnitude > 0)
            {
                StopAllCoroutines();
                StartCoroutine(!_isWalking ? _fovKick.FOVKickUp() : _fovKick.FOVKickDown());
            }

            return speed;
        }

        //当角色碰撞到刚体时，给刚体一个碰撞力
        private void OnControllerColliderHit(ControllerColliderHit hit)
        {
            Rigidbody body = hit.collider.attachedRigidbody;
            if (_collisionFlags == CollisionFlags.Below) return;
            if (body == null || body.isKinematic) return;
            body.AddForceAtPosition(_characterController.velocity * 0.1f, hit.point, ForceMode.Impulse);
        }
    }
}